<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Call Apply Bind 使用及实现</title>
</head>

<body>
  <script>
    /*
    一、实现 call() 方法
      1. 形参obj，this指向的值
      2. 形参args, 多个参数列表
    */
    Function.prototype.call = function (obj, ...args) {
      // 处理一下兼容性
      obj = obj || window
      // 当前哪个方法 调用这个 call 函数，当前this 就指向谁
      // 具体实现为, 给当前的obj添加一个自定义的属性，然后通过这个对象调用这个方法，就可以改变this 的指向
      obj.tempFn = this;
      obj.tempFn(...args);

      // 删除obj的tempFn 属性
      delete obj.tempFn;
    }

    /*
    二、实现 apply() 方法
      1. 形参obj，this指向的值
      2. 形参args, 接收一个数组
    */
    Function.prototype.apply = function (obj, args) {
      obj = obj || window
      // 当前哪个方法 调用这个 apply 函数，当前this 就指向谁
      // 与call实现的方法类似，只是接收的参数不同
      obj.tempFn = this;
      obj.tempFn(...args);

      // 删除obj的tempFn 属性
      delete obj.tempFn
    }

    /*
    三、实现 bind() 方法
      1. 形参obj，this指向的值
      2. 形参args, 多个参数列表
    */
    Function.prototype.bind = function (obj, ...args) {
      // 当前哪个方法 调用这个 apply 函数，当前this 就指向谁
      // 具体实现： 与call方法类似，不过要注意 参数列表有两个，一个是给原函数指定的参数列表，另一个是给新函数指定的参数列表
      
      // 返回的函数内部调用 call方法
      return (...args1) => {
        this.call(obj, ...args, ...args1);
      }
    }
  </script>

  <script>
    /* 
      1. call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

      2. apply  方法调用一个具有给定this值的函数，以及作为一个数组（或类似数组对象）提供的参数。
      
      // 注意：call() 方法的语法和作用与 apply() 方法类似，只有一个区别，就是 call() 方法接受的是一个参数列表，而 apply() 方法接受的是一个包含多个参数的数组。

      3. bind 方法创建一个新的函数，在bind()被调用时，这个原函数的this被bind的第一个参数指定，其余的参数将作为原函数的参数供调用时使用。
    */
    // 测试 call apply  bind
    function fn(a, b) {
      console.log(a, b, this);
    }

    let obj = {
      a: 1,
      b: 2
    }

    fn.call(obj, 1, 2); // 打印结果 1, 2, obj
    fn.apply(obj, [1, 2]); // 打印结果 1, 2， obj
    fn.bind(obj, 1)(); // 打印结果 1, undefined, obj
    fn.bind(obj, 1, 2)(); // 打印结果 1, 2, obj
    fn.bind(obj, 1)(4, 5); // 打印结果 1, 4, obj

    const fn1 = fn.bind(obj);
    fn1(5, 6); // 打印结果 5， 6, obj
  </script>


</body>

</html>